#include "./bmp.h"

/* == DECLARACION DE FUNCIONES AUXILIARES == */

void transcribirHeader (FILE* outStream, FILE* inStream, tBMPHeader header);

void transcribirPixels (FILE* outStream, FILE* inStream, tBMPHeader header, tOptTransform optTrans);

void bytecopy (unsigned char* out, int posOut,unsigned char* in, int posIn, int cntBytes);

void initBuffer (unsigned char* buffer, int length);

int bufferDataLength(unsigned char* buffer, int bufferLength);

void transformarFila (unsigned char* fila, tBMPHeader header, tOptTransform optTrans);

/* == FIN DE DECLARACION DE FUNCIONES AUXILIARES == */

/* == IMPLEMENTACION DE FUNCIONES BMP.H == */

void initOptions (tOptTransform* pOptTrans, tDescriptor pOpt) {
  
    // Inicializo trans;
    for (int i = 0; i < OPT_TRANSFORM_LENGTH; i++) (*pOptTrans)[i] = OPT_TRANSFORM_END;
    
    // Configuro la trasformacion por default.
    (*pOptTrans)[0] = 0;
    
    // Construyo los parametros de transformacion.
    // tDescriptor => a:b:c:...:n.  No supera las 10 divisiones (0~9).
    
    char tmp [2] = "0";

    for (int i = 0; i < strlen(pOpt); i+=2)
    {
      tmp[0] = pOpt[i];
      (*pOptTrans)[i / 2] = atoi(tmp);
    }
      
} // initOptions

int cntColumnasTransformacion (tOptTransform pOptTrans) {
    
  int i = 0;
  while (pOptTrans[i] != OPT_TRANSFORM_END && i < OPT_TRANSFORM_LENGTH) i++;
      
  return i;
  
} // cntColumnasTransformacion

int readBMPHeader(tpBMPHeader header, FILE* stream) {
    
  int result = 0;
  
  if (NULL != stream)
  {
    // File header.
    if (fread(&(header->type), sizeof (tUInt16), 1, stream)) result += 1; 
    if (fread(&(header->size), sizeof (tUInt32), 1, stream)) result += 1; 
    if (fread(&(header->reserve1), sizeof (tUInt16), 1, stream)) result += 1;
    if (fread(&(header->reserve2), sizeof (tUInt16), 1, stream)) result += 1;
    if (fread(&(header->offBits), sizeof (tUInt32), 1, stream)) result += 1;

    // BMP Header.
    if (fread(&(header->bmpSize), sizeof (tUInt32), 1, stream)) result += 1;
    if (fread(&(header->bmpWidth), sizeof (tUInt32), 1, stream)) result += 1;
    if (fread(&(header->bmpHeight), sizeof (tUInt32), 1, stream)) result += 1;
    if (fread(&(header->bmpPlanes), sizeof (tUInt16), 1, stream)) result += 1;
    if (fread(&(header->bmpBitCount), sizeof (tUInt16), 1, stream)) result += 1;
    if (fread(&(header->bmpCompression), sizeof (tUInt32), 1, stream)) result += 1;
    if (fread(&(header->bmpSizeImage), sizeof (tUInt32), 1, stream)) result += 1;
    if (fread(&(header->bmpXPelsPerMeter), sizeof (tUInt32), 1, stream)) result += 1;
    if (fread(&(header->bmpYPelsPerMeter), sizeof (tUInt32), 1, stream)) result += 1;
    if (fread(&(header->bmpClrUsed), sizeof (tUInt32), 1, stream)) result += 1;
    if (fread(&(header->bmpClrImportant), sizeof (tUInt32), 1, stream)) result += 1;
  }
  return result;
}

int printBMPHeader (FILE* stream, tBMPHeader pHeader) {
    
  int result = 0;       
  
  if (NULL != stream)       
  {
    result += fprintf(stream, "\n");
    
    if (result > 0)
    { // Pudo escribir con exito.  Sigo escribiendo.
      result += fprintf(stream, "Type:%hd\n", pHeader.type);
      result += fprintf(stream, "Size:%ld\n", pHeader.size); 
      result += fprintf(stream, "Reserve1:%hd\n", pHeader.reserve1); 
      result += fprintf(stream, "Reserve2:%hd\n", pHeader.reserve2);
      result += fprintf(stream, "OffBits:%ld\n", pHeader.offBits);
      result += fprintf(stream, "bmpSize:%ld\n", pHeader.bmpSize);     
      result += fprintf(stream, "bmpWidth:%ld\n", pHeader.bmpWidth);   
      result += fprintf(stream, "bmpHeight:%ld\n", pHeader.bmpHeight); 
      result += fprintf(stream, "bmpPlanes:%hd\n", pHeader.bmpPlanes); 
      result += fprintf(stream, "bmpBitCount:%hd\n", pHeader.bmpBitCount);
      result += fprintf(stream, "bmpCompression:%ld\n", pHeader.bmpCompression); 
      result += fprintf(stream, "bmpSizeImage:%ld\n", pHeader.bmpSizeImage); 
      result += fprintf(stream, "bmpXPelsPerMeter:%ld\n", pHeader.bmpXPelsPerMeter); 
      result += fprintf(stream, "bmpYPelsPerMeter:%ld\n", pHeader.bmpYPelsPerMeter); 
      result += fprintf(stream, "bmpClrUsed:%ld\n", pHeader.bmpClrUsed); 
      result += fprintf(stream, "bmpClrImportant:%ld\n\n", pHeader.bmpClrImportant);
    }
  }
  
  return result;
  
} // printfBMPHeader

void transformarBMP (FILE* outStream, FILE* inStream,tOptTransform optTrans) {
    
  // Se lee el header del archivo bmp de entrada.
  tBMPHeader header;
  readBMPHeader (&header, inStream);
  
  // Se verifica que el ancho en bits del archivo bmp de entrada sea directamente proporcional a la cantidad
  // de columnas a dividir la imagen.
  if ( 0 != header.bmpWidth % cntColumnasTransformacion(optTrans)) exit (FAIL);
   
  transcribirHeader (outStream, inStream, header);
  
  transcribirPixels (outStream, inStream, header, optTrans);
  
}

/* == FIN IMPLEMENTACION DE FUNCIONES BMP.H == */

/* == IMPLEMENTACION DE FUNCIONES AUXILIARES == */

void transcribirHeader (FILE* outStream, FILE* inStream, tBMPHeader header) {
    
    unsigned char buffer [100];
        
    fseek (inStream, 0, SEEK_SET);

    if (fread (buffer, 1, header.offBits, inStream))
    {
      fwrite(buffer, 1, header.offBits, outStream);
    }
}

void transcribirPixels (FILE* outStream, FILE* inStream, tBMPHeader header, tOptTransform optTrans) {
  
  // Defino la fila de pixels a procesar.
  
  int width = sizeof(tPixel24) * header.bmpWidth + header.bmpWidth % 4;  // Ancho de la fila de pixels en bytes.
  unsigned char* fila = malloc (width);
  initBuffer(fila, width);
  
  // Recorro secuencialmente fila por fila del archivo BMP de entrada, proceso la fila y la escribo
  // en el archivo de salida.
  
  while (fread (fila, width, 1, inStream))
  {
    transformarFila(fila, header, optTrans); // Aplico la transformacion.
    fwrite(fila, 1, width, outStream);
    initBuffer(fila, width);
  }

  free (fila);
}

void transformarFila (unsigned char* fila, tBMPHeader header, tOptTransform optTrans) {
  
    int cntCol = cntColumnasTransformacion(optTrans);
    int width = sizeof(tPixel24) * header.bmpWidth;      
    int segWidth = width / cntCol;

    unsigned char* filaTemp = malloc (width);
        
    for (int i = 0; i < cntCol; i++)
      bytecopy (filaTemp, i * segWidth, fila, optTrans[i] * segWidth, segWidth);

    bytecopy (fila, 0, filaTemp, 0, width );

    free (filaTemp);
}

void initBuffer (unsigned char* buffer, int length) {
  for (int i = 0; i < length; i++)
    buffer [i] = '\0';
}

int bufferDataLength(unsigned char* buffer, int bufferLength) {

  int length = 0;
  while ('\0' != buffer [length] && length < bufferLength) length++;  
  
  return length;
}

void bytecopy (unsigned char* out, int posOut,unsigned char* in, int posIn, int cntBytes) {
   memcpy (&out[posOut], &in[posIn], cntBytes);
}

/* == FIN DE IMPLEMENTACION DE FUNCIONES AUXILIARES == */ 
